using System;
using System.Collections;
using System.Collections.Generic;

namespace LightCAD.Three
{
    public class Points : Object3D, IMorphTargets, IGeometry, IMaterialObject, ISolid
    {
        #region scope properties or methods
        //private static Matrix4 _inverseMatrix = new Matrix4();
        //private static Ray _ray = new Ray();
        //private static Sphere _sphere = new Sphere();
        //private static Vector3 _position = new Vector3();
        private static PointsContext getContext() =>
            ThreadContext.getCurrThreadContext().PointsCtx;

        private static void testPoint(Vector3 point, int index, double localThresholdSq, Matrix4 matrixWorld, Raycaster raycaster, JsArr<Raycaster.Intersection> intersects, Object3D _object)
        {
            var ctx = getContext();
            var _ray = ctx._ray;
            var rayPointDistanceSq = _ray.distanceSqToPoint(point);
            if (rayPointDistanceSq < localThresholdSq)
            {
                var intersectPoint = new Vector3();
                _ray.closestPointToPoint(point, intersectPoint);
                intersectPoint.applyMatrix4(matrixWorld);

                var distance = raycaster.ray.origin.distanceTo(intersectPoint);


                if (distance < raycaster.near || distance > raycaster.far)
                    return;

                intersects.push(new Raycaster.Intersection()
                {
                    distance = distance,
                    distanceToRay = JMath.sqrt(rayPointDistanceSq),
                    point = intersectPoint,
                    index = index,
                    face = null,
                    target = _object
                });

            }

        }
        #endregion

        #region Properties

        public BufferGeometry geometry;
        public PointsMaterial material;

        #endregion
        #region IMorphTargets
        public JsArr<double> morphTargetInfluences { get; set; }
        public JsObj<int> morphTargetDictionary { get; set; }
        #endregion

        #region constructor
        public Points(BufferGeometry geometry = null, PointsMaterial material = null)
        {
            if (geometry == null) geometry = new BufferGeometry();
            if (material == null) material = new PointsMaterial();

            this.type = "Points";
            this.geometry = geometry;
            this.material = material;
            this.updateMorphTargets();
        }
        #endregion

        #region methods
        public Points copy(Points source, bool recursive)
        {
            base.copy(source, recursive);
            this.material = source.material;
            this.geometry = source.geometry;
            return this;
        }
        public override Object3D copy(Object3D source, bool recursive = true)
        {
            return copy(source as Points, recursive);
        }
        public override Object3D clone(bool recursive = true)
        {
            return new Points().copy(this, recursive);
        }
        public override void raycast(Raycaster raycaster, JsArr<Raycaster.Intersection> intersects = null, object vars = null)
        {
            if (intersects == null) intersects = new JsArr<Raycaster.Intersection>();
            var ctx = getContext();
            var _sphere = ctx._sphere;
            var _inverseMatrix = ctx._inverseMatrix;
            var _ray = ctx._ray;
            var _position = ctx._position;
            var geometry = this.geometry;
            var matrixWorld = this.matrixWorld;
            var threshold = raycaster._params.Points["threshold"];
            var drawRange = geometry.drawRange;
            // Checking boundingSphere distance to ray
            if (geometry.boundingSphere == null) geometry.computeBoundingSphere();
            _sphere.copy(geometry.boundingSphere);
            _sphere.applyMatrix4(matrixWorld);
            _sphere.radius += threshold;
            if (raycaster.ray.intersectsSphere(_sphere) == false) return;
            //
            _inverseMatrix.copy(matrixWorld).invert();
            _ray.copy(raycaster.ray).applyMatrix4(_inverseMatrix);
            var localThreshold = threshold / ((this.scale.x + this.scale.y + this.scale.z) / 3);
            var localThresholdSq = localThreshold * localThreshold;
            var index = geometry.index;
            var attributes = geometry.attributes;
            var positionAttribute = attributes.position;
            if (index != null)
            {
                var start = JMath.max(0, drawRange.start);
                var end = JMath.min(index.count, (drawRange.start + drawRange.count));
                for (int i = start, il = end; i < il; i++)
                {
                    var a = index.getIntX(i);
                    _position.fromBufferAttribute(positionAttribute, a);
                    testPoint(_position, a, localThresholdSq, matrixWorld, raycaster, intersects, this);
                }
            }
            else
            {
                var start = JMath.max(0, drawRange.start);
                var end = JMath.min(positionAttribute.count, (drawRange.start + drawRange.count));
                for (int i = start, l = end; i < l; i++)
                {
                    _position.fromBufferAttribute(positionAttribute, i);
                    testPoint(_position, i, localThresholdSq, matrixWorld, raycaster, intersects, this);
                }
            }
        }
        public void updateMorphTargets()
        {
            var geometry = this.geometry;
            var morphAttributes = geometry.morphAttributes;
            var keys = morphAttributes.Keys.ToJsArr();
            if (keys.Count > 0)
            {
                var morphAttribute = morphAttributes[keys[0]];
                if (morphAttribute != null)
                {
                    this.morphTargetInfluences = new JsArr<double>();
                    this.morphTargetDictionary = new JsObj<int>();
                    for (int m = 0, ml = morphAttribute.Count; m < ml; m++)
                    {
                        var name = morphAttribute[m]?.name ?? m.ToString();
                        this.morphTargetInfluences.push(0);
                        this.morphTargetDictionary[name] = m;
                    }
                }
            }
        }


        public Material getMaterial()
        {
            return this.material;
        }

        public void setMaterial(Material material)
        {
            this.material = material as PointsMaterial;
        }

        public JsArr<Material> getMaterials()
        {
            return new JsArr<Material> { this.material };
        }

        public void setMaterials(JsArr<Material> materials)
        {
            this.setMaterial(materials[0]);
        }

        public bool isMultiMaterial()
        {
            return false;
        }

        public BufferGeometry getGeometry()
        {
            return this.geometry;
        }

        public void setGeometry(BufferGeometry geo)
        {
            this.geometry = geo;
        }
        #endregion

    }
}
